/*
 * 
 */
package kds.network.http;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

import kds.network.HTTPCachePolicy;
import kds.network.HTTPNetworkConfig;
import kds.utils.debug.Debug;
import android.os.Environment;
import android.support.v4.util.LruCache;
import android.util.Log;

/**
 * The Class HTTPResponseCache.
 */
public class HTTPResponseCache {

	/** The shared cache. */
	private static HTTPResponseCache sharedCache;

	/** The mem cache. */
	private LruCache<String, HTTPResponseData> MemCache;

	/**
	 * Instantiates a new uRL reponse cache.
	 */
	protected HTTPResponseCache() {
		String mLocalCachePath = HTTPNetworkConfig.DefaultInternalCachePath
				.replace("%package%", HTTPNetworkConfig.ApplicationPackageName);
		File mLocalCacheFolder = new File(mLocalCachePath);
		if (!mLocalCacheFolder.exists()) {
			mLocalCacheFolder.mkdirs();
		}

		File mDiskCacheFolder = new File(
				HTTPNetworkConfig.DefaultExternalCachePath);
		if (!mDiskCacheFolder.exists()) {
			Log.e("HTTPResponseCache",
					"HTTPResponseCache:" + mLocalCacheFolder.getAbsolutePath()
							+ "not exist");
			mDiskCacheFolder.mkdirs();
		}

		// init LRU_CACHE_MEMORY
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

		// Use 1/4th of the available memory for this memory cache.
		final int cacheSize = maxMemory / 4;
		MemCache = new LruCache<String, HTTPResponseData>(cacheSize);

	}

	public static HTTPResponseCache getSharedCache() {
		if (sharedCache == null)
			sharedCache = new HTTPResponseCache();
		return sharedCache;

	}

	/**
	 * Save cache for request.
	 * 
	 * @param url
	 *            the url
	 * @param data
	 *            the data
	 * @param policy
	 *            the policy
	 */
	public void saveCacheForRequest(String url, HTTPResponseData data,
			HTTPCachePolicy policy) {
		if (data == null || data.getBytes() != null)
			return;
		if (policy == HTTPCachePolicy.MEMCACHE) {
			MemCache.put(genURLCacheKey(url), data);
			return;
		} else if (policy == HTTPCachePolicy.NONE
				| policy == HTTPCachePolicy.SYNC_NONE) {
			if (checkDiskMounted()) {
				policy = HTTPCachePolicy.DISK;
			} else {
				policy = HTTPCachePolicy.LOCAL;
			}
		}
		StringBuilder builder = new StringBuilder();
		File cacheRequest = new File(builder.append(getCachePath(policy))
				.append("/").append(genURLCacheKey(url)).toString());
		if (!data.writeToFile(cacheRequest)) {
			Debug.error("HTTPResponseCache:Cann't save file"
					+ cacheRequest.getAbsolutePath());
		}

	}

	/**
	 * Load cache for url.
	 * 
	 * @param url
	 *            the url
	 * @param policy
	 *            the policy
	 * @return the kD data
	 * @throws Exception
	 */
	public HTTPResponseData loadCacheForUrl(String url, HTTPCachePolicy policy,
			long timeExpired) throws Exception {

		if (policy == HTTPCachePolicy.MEMCACHE) {
			HTTPResponseData data = MemCache.get(genURLCacheKey(url));
			if (data != null) {
				return data;
			} else {
				return null;
			}
		} else if (policy == HTTPCachePolicy.NONE) {
			// if policy is none then check SDcard mounted
			// SDCard in higher priority
			if (checkDiskMounted()) {
				policy = HTTPCachePolicy.DISK;
			} else {
				policy = HTTPCachePolicy.LOCAL;
			}
		} else if (policy == HTTPCachePolicy.SYNC_NONE) {
			// if policy is none then check SDcard mounted
			// SDCard in higher priority
			if (checkDiskMounted()) {
				policy = HTTPCachePolicy.SYNC_DISK;
			} else {
				policy = HTTPCachePolicy.SYNC_LOCAL;
			}
		}

		StringBuilder builder = new StringBuilder();
		File cacheRequest = new File(builder.append(getCachePath(policy))
				.append("/").append(genURLCacheKey(url)).toString());
		if (cacheRequest.exists()) {
			// if no sync then response cache
			if (policy != HTTPCachePolicy.SYNC_LOCAL
					&& policy != HTTPCachePolicy.SYNC_DISK)
				return new HTTPResponseData(cacheRequest);

			else {// if sync then check time expired
				HTTPResponseData data = new HTTPResponseData(cacheRequest);
				Debug.debug("Cache for url: " + url + " has time expired: "
						+ timeExpired);
				long lastTime = cacheRequest.lastModified();
				Date nowDate = new Date();
				long nowTime = nowDate.getTime();
				if (nowTime - lastTime > timeExpired) {
					Debug.debug("Cache for url: " + url + " is expired");
					data.setExpired(true);
				} else {
					Debug.debug("Cache for url: " + url + " not expired");
					data.setExpired(false);
				}

				return data;
			}
		}
		return null;
	}

	/**
	 * Gets the cache path.
	 * 
	 * @param policy
	 *            the policy
	 * @return the cache path
	 */
	private String getCachePath(HTTPCachePolicy policy) {
		if (policy == HTTPCachePolicy.LOCAL
				| policy == HTTPCachePolicy.SYNC_LOCAL) {
			return HTTPNetworkConfig.DefaultInternalCachePath.replace(
					"%package%", HTTPNetworkConfig.ApplicationPackageName);
		} else {
			return HTTPNetworkConfig.DefaultExternalCachePath;
		}
	}

	/**
	 * Clear mem cache.
	 */
	public void clearMemCache() {
		MemCache.evictAll();
	}

	/**
	 * Check disk mounted.
	 * 
	 * @return true, if successful
	 */
	private static boolean checkDiskMounted() {
		String state = Environment.getExternalStorageState();
		return Environment.MEDIA_MOUNTED.equals(state);
	}

	/**
	 * Gen url cache key.
	 * 
	 * @param url
	 *            the url
	 * @return the string
	 */
	private static String genURLCacheKey(String url) {
		return generateMD5(url);
	}

	/**
	 * Generate m d5.
	 * 
	 * @param value
	 *            the value
	 * @return the string
	 */
	public static String generateMD5(String value) {
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] bytes;
			try {
				bytes = value.getBytes("UTF-8");
			} catch (UnsupportedEncodingException e1) {
				bytes = value.getBytes();
			}
			StringBuilder result = new StringBuilder();
			for (byte b : md.digest(bytes)) {
				result.append(Integer.toHexString((b & 0xf0) >>> 4));
				result.append(Integer.toHexString(b & 0x0f));
			}
			return result.toString();
		} catch (NoSuchAlgorithmException ex) {
			throw new RuntimeException(ex);
		}
	}

}
